﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace PROJECT_BOMBER.BLL
{
    public abstract class ElementPhysique
    {
        #region Fields

        internal Vector2 _positionbase;

        public Vector2 Positionbase
        {
            get { return _positionbase; }
            set { _positionbase = value; }
        }

        internal Vector2 _position;

        public Vector2 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        internal CollisionStrategy _collisionStrategy;

        public CollisionStrategy CollisionStrategy
        {
            get { return _collisionStrategy; }
            set { _collisionStrategy = value; }
        }

        #endregion

        #region Constructeur

        public ElementPhysique(int x, int y) : this(x, y, 0, 0) { }

        public ElementPhysique(int x, int y, int width, int height)
        {
            Positionbase = new Vector2(x, y);
            Position = new Vector2(x, y);
            Animations = new Dictionary<string,AnimationSprite>();
            Width = width;
            Height = height;
            CollisionStrategy = new GroundCollision();
        }

        #endregion

        #region Visibilité

        bool _visible;

        public bool Visible
        {
            get { return _visible; }
            set { _visible = value; }
        }

        #endregion

        #region Update

        public virtual void Update(Level level)
        {

            _position.X = level.Position.X + _positionbase.X;
            _position.Y = level.Position.Y + _positionbase.Y;

            Onscreen();
           
            if (Visible)
            {
                Case = new Rectangle((int)_position.X, (int)_position.Y, (int)Width, (int)Height);
                CurrentAnimation.Update(Case.X, Case.Y);
            }


        }

        public virtual void Update(GameTime gametime, GraphicsDeviceManager graphics, Camera2d cam, Level level,Vector2 pos)
        {

            _position = pos;

            Onscreen();

            if (Visible)
            {
                Case = new Rectangle((int)_position.X, (int)_position.Y, (int)Width, (int)Height);
                if (CurrentAnimation != null)
                CurrentAnimation.Update(Case.X, Case.Y);
            }


        }

        #endregion

        #region Rectangle

        public Rectangle Case = new Rectangle();

        int _width;
        public int Width
        {
            get { return _width; }
            set { _width = value; }
        }

        int _height;
        public int Height
        {
            get { return _height; }
            set { _height = value; }
        }

        #endregion

        #region AnimationSprite

        public void AddAnimation(string strg, AnimationSprite anim)
        {
            Animations.Add(strg, anim);
            if (Animations["default"].GetType() == typeof(AnimationSprite))
            {
                CurrentAnimation = Animations["default"];
                Width = Animations["default"].Definition.FrameSize.X ;
                Height = Animations["default"].Definition.FrameSize.Y;
            }
        }

        private Dictionary<string,AnimationSprite> _animation;

        public virtual Dictionary<string, AnimationSprite> Animations
        {
            get { return _animation; }
            set { _animation = value; }
        }

        private AnimationSprite _currentAnimation;

        public AnimationSprite CurrentAnimation
        {
            get { return _currentAnimation; }
            set { _currentAnimation = value; }
        }

        #region AnimationSprite Base

        AnimationSprite _sprite;
        public AnimationSprite Sprite()
        {
            return _sprite;
        }

        public void Sprite(AnimationSprite sprite)
        {
            _sprite = sprite;
        }

       


        public void Sprite(AnimationDefinition anim, Game game, bool taille = true)
        {
            if (taille)
            {
                Width = anim.FrameSize.X;
                Height = anim.FrameSize.Y;
            }
            Case = new Rectangle((int)Positionbase.X, (int)Positionbase.Y, Width, Height);
            _sprite = new AnimationSprite(game, anim);
            _sprite.Position = new Point((int)Positionbase.X, (int)Positionbase.Y);
        }

        #endregion


        public virtual void LoadAnimations(SpriteBatch spriteBatch)
        {
            foreach (KeyValuePair<string, AnimationSprite> anim in Animations)
            {
                anim.Value.LoadContent(spriteBatch);
            }
        }

        public virtual void DrawAnimation()
        {
            if (Visible && CurrentAnimation != null)
            {
                    CurrentAnimation.Draw();
            }
        }

        public virtual void UpdateAnimation()
        {
            if (Visible)
            {
                foreach (KeyValuePair<string, AnimationSprite> anim in Animations)
                {
                    anim.Value.Update(Case.X, Case.Y);
                }
            }
        }

        public virtual void InitializeAnimations()
        {
            foreach (KeyValuePair<string, AnimationSprite> anim in Animations)
            {
                anim.Value.Initialize();
            }
        }

        internal void Onscreen()
        {
            if (Globals.Camera.Pos.Y - _position.Y <= Globals.GDM.PreferredBackBufferHeight / (Globals.Camera.Zoom * 2) + Height && Globals.Camera.Pos.Y - _position.Y >= -Globals.GDM.PreferredBackBufferHeight / (Globals.Camera.Zoom * 2)
                && Globals.Camera.Pos.X - _position.X <= Globals.GDM.PreferredBackBufferWidth / (Globals.Camera.Zoom * 2) + Width && Globals.Camera.Pos.X - _position.X >= -Globals.GDM.PreferredBackBufferWidth / (Globals.Camera.Zoom * 2))
            {
                Visible = true;
            }
            else
            {
                Visible = false;
            }
        }

        #endregion
        
        #region intersect

        public bool IntersectPX(ElementMove element)
        {
            #region character

            bool valuereturn = false;

            AnimationSprite animation = CurrentAnimation;

            if (Case.Intersects(element.Case) == true)
            {

                Rectangle rectangleA = RectangleImg(element);

                Rectangle rectangleB = RectangleDiffPlan(element);

                Rectangle rectangleC = RectangleLimit(element, rectangleB);


                #region comparaison dans les limites des paires de rectangles chaque pixel et incrementant le compteur [valuereturn] quand 2 pixels ne sont pas transparent

                    // mise en tableau des pixels des images
                    Color[] dataA = ExtractColor(element, rectangleA);

                    Color[] dataB = ExtractColor((ElementPhysique)this, rectangleC);

                    if (dataA != null && dataB != null)
                    {
                        // determine limites a étudier
                        int top = Math.Max(rectangleA.Top, rectangleB.Top);
                        int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
                        int left = Math.Max(rectangleA.Left, rectangleB.Left);
                        int right = Math.Min(rectangleA.Right, rectangleB.Right);

                        #region comparaison des pixels
                        for (int y = bottom - 1; y >= top; y--)
                        {
                            for (int x = left; x < right; x++)
                            {
                                Color colorA = dataA[(x - rectangleA.Left) +
                                                        (y - rectangleA.Top) *
                                                        rectangleA.Width];
                                Color colorB = dataB[(x - rectangleB.Left) +
                                                        (y - rectangleB.Top) *
                                                        rectangleB.Width];
                                if (colorA.A != 0 && colorB.A != 0)
                                {
                                    valuereturn = true;
                                }
                            }
                        }
                    }
                        #endregion

             

                #endregion

            }
            
            return valuereturn;

            #endregion
        }

        /// <summary>
        /// Extracts the color.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="rect">The rect.</param>
        /// <returns></returns>
        private static Color[] ExtractColor(ElementPhysique element, Rectangle rect)
        {
            Color[] dataA = new Color[Math.Max(rect.Width, 0) * Math.Max(rect.Height, 0)];
            if (dataA.Count<Color>() > 0)
            {
                element.CurrentAnimation.Sprite.GetData(0, rect, dataA, 0, Math.Max(rect.Width, 0) * Math.Max(rect.Height, 0));
                return dataA;
            }
            else
            {
                return null;
            }
            
        }

        /// <summary>
        /// Return the rectangle difference.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="rectangleB">The rectangle B.</param>
        /// <returns></returns>
        private Rectangle RectangleLimit(ElementMove element, Rectangle rectangleB)
        {
            #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport à l'element et limitée à la zone concernée

            int topY = Math.Max((int)element.Position.Y - (int)Position.Y, 0);
            int leftX = Math.Max((int)element.Position.X - (int)Position.X, 0);

            int depassementX = Math.Max(leftX + rectangleB.Width - Width, 0);
            int depassementY = Math.Max(topY + rectangleB.Height - Height, 0);

            Rectangle rectangleC = new Rectangle(leftX, topY, rectangleB.Width - depassementX, rectangleB.Height - depassementY);

            #endregion
            return rectangleC;
        }

        /// <summary>
        /// return a rectangle with other plan than img.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        private Rectangle RectangleDiffPlan(ElementMove element)
        {
            #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport a l'image du perso et limitée à la zone concernée

            int bottomY = Math.Min((int)(element.Position.Y + element.Height), (int)(Position.Y + Height));
            int topY = Math.Max((int)element.Position.Y, (int)Position.Y);
            int leftX = Math.Max((int)element.Position.X, (int)Position.X);
            int rightX = Math.Min((int)element.Position.X + element.Width, (int)Position.X + Width);

            Rectangle rectangleB = new Rectangle(0, 0, rightX - leftX, bottomY - topY);

            return rectangleB;

            #endregion
        }

        /// <summary>
        /// return a rectangle with the img in plan.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        private static Rectangle RectangleImg(ElementMove element)
        {
            #region Rectangles du personnage dont les coordonnées X Y sont calculés par rapport a l'image du perso

            Rectangle rectangleA;
            rectangleA = new Rectangle(0, 0, element.Width, element.Height);

            #endregion

            return rectangleA;
        }

        public virtual int[] CalcIntersect(ElementMove element)
        {
            
            #region character

            int[] valuereturn = new int[4]{0,0,0,0};
            int imgX = 0;

            AnimationSprite animation = this.CurrentAnimation;

            if (this.Case.Intersects(element.Case) == true)
            {

                #region Rectangles du personnage dont les coordonnées X Y sont calculés par rapport a l'image du perso

                Rectangle[] rectangleA = new Rectangle[4];

                rectangleA[0] = new Rectangle(0, 0, element.Width / 2, element.Height / 2);
                rectangleA[1] = new Rectangle(element.Width / 2, 0, element.Width / 2, element.Height / 2);
                rectangleA[2] = new Rectangle(element.Width / 2, element.Height / 2, element.Width / 2, element.Height / 2);
                rectangleA[3] = new Rectangle(0, element.Height / 2, element.Width / 2, element.Height / 2);

                #endregion

                #region Rectangles du personnage dont les coordonnées X Y sont calculés par rapport à la fenetre de jeu

                Rectangle[] rectangleF = new Rectangle[4];

                rectangleF[0] = new Rectangle((int)element.Position.X, (int)element.Position.Y, element.Width / 2, element.Height / 2);
                rectangleF[1] = new Rectangle((int)element.Position.X + element.Width / 2, (int)element.Position.Y, element.Width / 2, element.Height / 2);
                rectangleF[2] = new Rectangle((int)element.Position.X + element.Width / 2, (int)element.Position.Y + element.Height / 2, element.Width / 2, element.Height / 2);
                rectangleF[3] = new Rectangle((int)element.Position.X, (int)element.Position.Y + element.Height / 2, element.Width / 2, element.Height / 2);

                #endregion


                #region calcul des limites de taille des rectangles du niveau par rapport a la zone étudiée

                int bottomY = Math.Max(-(int)(element.Position.Y + element.Height), 0);
                int topY = Math.Min(-(int)element.Position.Y, animation.Sprite.Height);
                int leftX = Math.Max((int)element.Position.X, animation.Definition.FrameSize.X * imgX - animation.Sprite.Width);
                int rightX = Math.Min((int)element.Position.X + element.Width, animation.Sprite.Width + animation.Definition.FrameSize.X * imgX - animation.Sprite.Width);
                
                #endregion

                #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport a l'image du perso et limitée à la zone concernée

                Rectangle rectangleB = new Rectangle(leftX, (int)element.Position.Y, rightX - leftX, topY - bottomY);
                Rectangle[] rectangleD = new Rectangle[4];

                rectangleD[0] = new Rectangle(rectangleB.X, rectangleB.Y, rectangleB.Width / 2, rectangleB.Height / 2);
                rectangleD[1] = new Rectangle(rectangleB.X + rectangleB.Width / 2, rectangleB.Y, rectangleB.Width / 2, rectangleB.Height / 2);
                rectangleD[2] = new Rectangle(rectangleB.X + rectangleB.Width / 2, rectangleB.Y + rectangleB.Height / 2, rectangleB.Width / 2, rectangleB.Height / 2);
                rectangleD[3] = new Rectangle(rectangleB.X, rectangleB.Y + rectangleB.Height / 2, rectangleB.Width / 2, rectangleB.Height / 2);

                #endregion

                #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport à la fenetre de jeu et limitée à la zone concernée

                Rectangle rectangleC = new Rectangle(leftX - animation.Definition.FrameSize.X * imgX + animation.Sprite.Width, animation.Sprite.Height - topY - animation.Definition.FrameSize.Y + animation.Sprite.Height, rightX - leftX, topY - bottomY);
                Rectangle[] rectangleE = new Rectangle[4];

                rectangleE[0] = new Rectangle(rectangleC.X, rectangleC.Y, rectangleC.Width / 2, rectangleC.Height / 2);
                rectangleE[1] = new Rectangle(rectangleC.X + rectangleC.Width / 2, rectangleC.Y, rectangleC.Width / 2, rectangleC.Height / 2);
                rectangleE[2] = new Rectangle(rectangleC.X + rectangleC.Width / 2, rectangleC.Y + rectangleC.Height / 2, rectangleC.Width / 2, rectangleC.Height / 2);
                rectangleE[3] = new Rectangle(rectangleC.X, rectangleC.Y + rectangleC.Height / 2, rectangleC.Width / 2, rectangleC.Height / 2);

                #endregion


                #region comparaison dans les limites des paires de rectangles chaque pixel et incrementant le compteur [valuereturn] quand 2 pixels ne sont pas transparent

                for (int i = 0; i < 4; i++)
                {
                    // mise en tableau des pixels du perso
                    Color[] dataA = new Color[rectangleA[i].Width * rectangleA[i].Height];
                    element.CurrentAnimation.Sprite.GetData(0, rectangleA[i], dataA, 0, rectangleA[i].Width * rectangleA[i].Height);


                    Color[] dataB = new Color[rectangleD[i].Width * rectangleD[i].Height];
                    
                    if (rectangleD[i].Width * rectangleD[i].Height > 0)
                    {
                        //mise en tableau des pixels du niveau
                        animation.Sprite.GetData(0, rectangleE[i], dataB, 0, rectangleD[i].Width * rectangleD[i].Height);

                        // determine limites a étudier
                        int top = Math.Max(rectangleF[i].Top, rectangleD[i].Top);
                        int bottom = Math.Min(rectangleF[i].Bottom, rectangleD[i].Bottom);
                        int left = Math.Max(rectangleF[i].Left, rectangleD[i].Left);
                        int right = Math.Min(rectangleF[i].Right, rectangleD[i].Right);

                        #region comparaison des pixels
                        for (int y = bottom - 1; y >= top; y--)
                        {
                            for (int x = left; x < right; x++)
                            {
                                Color colorA = dataA[(x - rectangleF[i].Left) +
                                                        (y - rectangleF[i].Top) *
                                                        rectangleF[i].Width];
                                Color colorB = dataB[(x - rectangleD[i].Left) +
                                                        (y - rectangleD[i].Top) *
                                                        rectangleD[i].Width];
                                if (colorA.A != 0 && colorB.A != 0)
                                {
                                    valuereturn[i]++;
                                }
                            }
                        }
                        #endregion
                    }

                }

                #endregion

            }
            
            return valuereturn;

            #endregion
            
        }


        public virtual double AnglePX(ElementMove element)
        {

            #region character

            int[] valuereturn = new int[2]{0,0};

            #region determine pixels de pente

            AnimationSprite animation = CurrentAnimation;  //image taille x   2001

            if (Case.Intersects(element.Case) == true && element.Position.Y - element.Height > 0) //TODO change positioncontrol
            {

                #region Rectangles du personnage dont les coordonnées X Y sont calculés par rapport a l'image du perso

                Rectangle[] rectangleA = new Rectangle[4];

                rectangleA[0] = new Rectangle(0, element.Height / 2, 1, element.Height);
                rectangleA[1] = new Rectangle(element.Width - 1, element.Height / 2, 1, element.Height);


                #endregion

                #region Rectangles du personnage dont les coordonnées X Y sont calculés par rapport à la fenetre de jeu

                Rectangle[] rectangleF = new Rectangle[4];

                rectangleF[0] = new Rectangle((int)element.Position.X, (int)element.Position.Y + element.Height / 2, 1, element.Height);
                rectangleF[1] = new Rectangle((int)element.Position.X + element.Width - 1, (int)element.Position.Y + element.Height / 2, 1, element.Height);

                #endregion


                #region calcul des limites de taille des rectangles du niveau par rapport a la zone étudiée

                int bottomY = Math.Max(-(int)(element.Position.Y + element.Height), 0);
                int topY = Math.Min(-(int)element.Position.Y, animation.Sprite.Height);
                int leftX = Math.Max((int)element.Position.X, animation.Definition.FrameSize.X - animation.Sprite.Width);
                int rightX = Math.Min((int)element.Position.X + element.Width, animation.Sprite.Width + animation.Definition.FrameSize.X - animation.Sprite.Width);

                #endregion

                #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport a l'image du perso et limitée à la zone concernée

                Rectangle rectangleB = new Rectangle(leftX, (int)element.Position.Y, rightX - leftX, topY - bottomY);
                Rectangle[] rectangleD = new Rectangle[4];

                rectangleD[0] = new Rectangle(rectangleB.X, rectangleB.Y + rectangleB.Height / 2, 1, rectangleB.Height);
                rectangleD[1] = new Rectangle(rectangleB.X + rectangleB.Width - 1, rectangleB.Y + rectangleB.Height / 2, 1, rectangleB.Height);

                #endregion

                #region Rectangles du niveau dont les coordonnées X Y sont calculés par rapport à la fenetre de jeu et limitée à la zone concernée

                Rectangle rectangleC = new Rectangle(leftX - animation.Definition.FrameSize.X + animation.Sprite.Width,
                                                        animation.Sprite.Height - topY - animation.Definition.FrameSize.Y + animation.Sprite.Height,
                                                        rightX - leftX,
                                                        topY - bottomY);
                Rectangle[] rectangleE = new Rectangle[4];

                rectangleE[0] = new Rectangle(rectangleC.X, rectangleC.Y + rectangleC.Height / 2, 1, rectangleC.Height);
                rectangleE[1] = new Rectangle(rectangleC.X + rectangleC.Width - 1, rectangleC.Y + rectangleC.Height / 2, 1, rectangleC.Height);

                #endregion


                #region comparaison dans les limites des paires de rectangles chaque pixel et incrementant le compteur [valuereturn] quand 2 pixels ne sont pas transparent

                for (int i = 0; i < 2; i++)
                {
                    Color[] dataB = new Color[rectangleD[i].Width * rectangleD[i].Height];

                    if (rectangleD[i].Width * rectangleD[i].Height > 0)
                    {
                        //mise en tableau des pixels du niveau
                        animation.Sprite.GetData(0, rectangleE[i], dataB, 0, rectangleD[i].Width * rectangleD[i].Height);

                        // determine limites a étudier
                        int top = rectangleD[i].Top;
                        int bottom = rectangleD[i].Bottom;
                        int left = rectangleD[i].Left;
                        int right = rectangleD[i].Right;

                        #region comparaison des pixels
                        for (int y = bottom - 1; y >= top; y--)
                        {
                            for (int x = left; x < right; x++)
                            {
                                Color colorB = dataB[(x - rectangleD[i].Left) +
                                                        (y - rectangleD[i].Top) *
                                                        rectangleD[i].Width];
                                if (colorB.A != 0)
                                {
                                    valuereturn[i]++;
                                }
                            }
                        }
                        #endregion
                    }

                }
                #endregion
            }

            #endregion

            #region calcul de l'angle de pente
            if ((valuereturn[0] > element.Height / 2 || valuereturn[1] > element.Height / 2) && valuereturn[0] > 0 && valuereturn[1] > 0)
            {
                int H = valuereturn[0] - valuereturn[1];
                int L = element.Width;
                double Angle = Math.Atan((float)(-H) / (float)L) * 180 / Math.PI;
                if (Angle < 20)
                {
                    return Angle;
                }
                else
                {
                    return 0;
                }
            }
            else
            {
                return 0;
            }
            #endregion


            #endregion
        }

        #endregion

    }
}
